from typing import Optional, Tuple
from .Exporter import PendulumState
import math
import cv2
import numpy as np
from loguru import logger


HSV_LOWER = [40, 70, 70]
HSV_UPPER = [80, 255, 255]


class Detector:
    """识别器基类."""

    pass


class Detector_HSV(Detector):
    """识别器类,使用HSV法."""

    """识别器类,使用HSV法."""

    def __init__(self, lower: list = None, upper: list = None):
        if lower is None:
            lower = HSV_LOWER
        if upper is None:
            upper = HSV_UPPER
        _lower = np.array(lower)
        _upper = np.array(upper)
        PendulumState.setHsv(_lower, _upper)
        self.physicalLength: float = 120.0

    def setPhysicalLength(self, Length: float) -> None:
        self.physicalLength = Length

    def calibrate(self, hsv_frame, roi) -> None:
        try:
            x, y, w, h = roi
            hsv_height, hsv_width = hsv_frame.shape[:2]
            x = max(0, min(x, hsv_width - 1))
            y = max(0, min(y, hsv_height - 1))
            w = max(1, min(w, hsv_width - x))
            h = max(1, min(h, hsv_height - y))
            roi_hsv = hsv_frame[y : y + h, x : x + w]
            if roi_hsv.size == 0:
                logger.warning("ROI区域为空，无法进行HSV校准")
                return
            mean_hsv = cv2.mean(roi_hsv)[:3]
            std_hsv = np.std(roi_hsv.reshape(-1, 3), axis=0)

            h_tol = max(5, std_hsv[0] * 2)
            s_tol = max(30, std_hsv[1] * 2)
            v_tol = max(30, std_hsv[2] * 2)

            tol = np.array([h_tol, s_tol, v_tol])

            new_lower = np.clip(
                np.array(mean_hsv) - tol, [0, 0, 0], [179, 255, 255]
            ).astype(int)
            new_upper = np.clip(
                np.array(mean_hsv) + tol, [0, 0, 0], [179, 255, 255]
            ).astype(int)

            if np.any(new_upper - new_lower < [5, 10, 10]):
                logger.warning("计算出的HSV阈值范围太小，使用默认容差")
                tol = np.array([10, 50, 50])
                new_lower = np.clip(
                    np.array(mean_hsv) - tol, [0, 0, 0], [179, 255, 255]
                ).astype(int)
                new_upper = np.clip(
                    np.array(mean_hsv) + tol, [0, 0, 0], [179, 255, 255]
                ).astype(int)

            PendulumState.setHsv(new_lower, new_upper)

            logger.info(f"HSV校准成功: ROI=({x},{y},{w},{h})")
            logger.info(f"平均HSV: {mean_hsv}")
            logger.info(f"HSV标准差: {std_hsv}")
            logger.info(
                f"新HSV阈值: 下限={PendulumState.hsv_lower.tolist()}, 上限={PendulumState.hsv_upper.tolist()}"
            )
        except Exception as e:
            logger.error(f"HSV校准失败: {str(e)}")
            PendulumState.setHsv(np.array(HSV_LOWER), np.array(HSV_UPPER))

    def getHsvMask(
        self, frame
    ):
        self.hsv_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        lower = np.array(PendulumState.hsv_lower)
        upper = np.array(PendulumState.hsv_upper)
        self.mask_frame = cv2.inRange(self.hsv_frame, lower, upper)
        return self.mask_frame, self.hsv_frame

    def getBallCenter(
        self, mask_frame = None
    ) -> Tuple[Optional[Tuple[int, int]], Optional[np.ndarray]]:
        if mask_frame is None:
            mask_frame = self.mask_frame
        contours, _ = cv2.findContours(
            mask_frame, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
        )
        if contours:
            c = max(contours, key=cv2.contourArea)
            M = cv2.moments(c)
            if M["m00"] != 0:
                cx = int(M["m10"] / M["m00"])
                cy = int(M["m01"] / M["m00"])
                return (cx, cy), c
        return None, None


class Analyzer:
    def __init__(self, alpha: float = 0.8):
        self.PIVOT_POINT: Optional[Tuple[int, int]] = None
        self.ORIGIN_POINT: Optional[Tuple[int, int]] = None
        self.REFERENCE_ANGLE: Optional[float] = None
        self.alpha = alpha
        self._prev_angle: Optional[float] = None

    def updateMouse(self, event, x, y, flags, param):
        if event == cv2.EVENT_LBUTTONDOWN:
            self.PIVOT_POINT = (x, y)
            self.REFERENCE_ANGLE = None
            logger.info(f"已设置摆轴固定点: {self.PIVOT_POINT},清除参考角度")
            PendulumState.update(
                pivot_x=float(x), pivot_y=float(y), reference_angle=0.0
            )
        elif event == cv2.EVENT_RBUTTONDOWN:
            self.ORIGIN_POINT = (x, y)
            logger.info(f"已设置坐标原点: {self.ORIGIN_POINT}")

    def getAngleAndLine(
        self, frame: np.ndarray, ball_center: Tuple[int, int]
    ) -> Optional[Tuple[float, Tuple[int, int, int, int], Tuple[int, int]]]:
        if not ball_center:
            return None, None, None

        if self.PIVOT_POINT and self.PIVOT_POINT[0] is not None:
            dx = ball_center[0] - self.PIVOT_POINT[0]
            dy = ball_center[1] - self.PIVOT_POINT[1]
            angle = math.degrees(math.atan2(dx, dy))
            if self.REFERENCE_ANGLE is None:
                self.REFERENCE_ANGLE = angle
            angle_rel = angle - self.REFERENCE_ANGLE
            line = (*self.PIVOT_POINT, *ball_center)
        else:
            edges = cv2.Canny(cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY), 50, 150)
            angle_raw, line = self._getHoughAngle(edges, ball_center)
            if angle_raw is not None:
                if self.REFERENCE_ANGLE is None:
                    self.REFERENCE_ANGLE = angle_raw
                angle_rel = angle_raw - self.REFERENCE_ANGLE
            else:
                return None, None, None

        if self._prev_angle is not None:
            angle_rel = self.alpha * self._prev_angle + (1 - self.alpha) * angle_rel
        self._prev_angle = angle_rel

        x0 = ball_center[0] - (self.ORIGIN_POINT[0] if self.ORIGIN_POINT else 0)
        y0 = ball_center[1] - (self.ORIGIN_POINT[1] if self.ORIGIN_POINT else 0)

        return angle_rel, line, (x0, y0)

    def _getHoughAngle(
        self, edges: np.ndarray, ball_center: Tuple[int, int]
    ) -> Tuple[Optional[float], Optional[Tuple[int, int, int, int]]]:
        x, y = ball_center
        region = edges[max(0, y - 200) : y, max(0, x - 50) : x + 50]
        if region.size == 0:
            return None, None
        lines = cv2.HoughLinesP(
            region, 1, np.pi / 180, 30, minLineLength=30, maxLineGap=10
        )
        best_line = None
        best_dist = float("inf")
        best_angle = None
        if lines is not None:
            for line in lines:
                x1, y1, x2, y2 = line[0]
                gx1, gy1 = x1 + x - 50, y1 + y - 200
                gx2, gy2 = x2 + x - 50, y2 + y - 200
                dist = min(np.hypot(gx1 - x, gy1 - y), np.hypot(gx2 - x, gy2 - y))
                if dist < best_dist:
                    best_dist = dist
                    best_line = (gx1, gy1, gx2, gy2)
                    dx, dy = gx2 - gx1, gy2 - gy1
                    best_angle = math.degrees(math.atan2(dx, dy))
        return best_angle, best_line

    def setReferenceAngle(self, ball_center: Tuple[int, int]) -> None:
        if not self.PIVOT_POINT or not ball_center:
            return
        dx = ball_center[0] - self.PIVOT_POINT[0]
        dy = ball_center[1] - self.PIVOT_POINT[1]
        self.REFERENCE_ANGLE = math.degrees(math.atan2(dx, dy))
        logger.info(f"参考角度设为: {self.REFERENCE_ANGLE:.2f} deg")

    def cleanRef(self) -> None:
        self.PIVOT_POINT = None
        self.ORIGIN_POINT = None
        self.REFERENCE_ANGLE = None
        self._prev_angle = None
        logger.info("已删除摆轴固定点和参考角度")

    def refPrint(self) -> None:
        logger.info(self.PIVOT_POINT)
        logger.info(self.ORIGIN_POINT)
        logger.info(self.REFERENCE_ANGLE)
